package com.afdor.rws.dlock.jedis.sharded;

import com.afdor.rws.annotation.extension.Extension;
import com.afdor.rws.dlock.lock.ReentrantLock;
import com.afdor.rws.dlock.lock.ReentrantReadWriteLock;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import redis.clients.jedis.ShardedJedisPool;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 采用jedis客户端实现的基于redis的分布式锁，该锁效率高，但存在可用性问题，不能用于要求高稳定性环境或其他敏感业务场景，请谨慎使用
 *
 * @author 悭梵
 * @date Created in 2018-09-04 17:08
 */
@Slf4j
@ToString
@EqualsAndHashCode
@Extension(value = "shardedJedis", scope = Extension.Scope.PROTOTYPE, interfaceClass = ReentrantReadWriteLock.class)
public class DistributedReentrantReadWriteLock implements ReentrantReadWriteLock {

    private static final String READ_LOCK_NAME = "Read/";
    private static final String WRITE_LOCK_NAME = "Write/";
    private static final String ROOT_PATH = "/Distributed/ReentrantReadWriteLock/";

    /**
     * 重试等待时间
     */
    @Getter
    private int retryAwait = 300;
    @Getter
    private int lockTimeout = 2000;

    private final String lockId;
    private final String readLockId;
    private final String writeLockId;
    private final ReentrantLock readMutex;
    private final ReentrantLock writeMutex;
    private final ShardedJedisPool shardedJedisPool;

    public DistributedReentrantReadWriteLock(ShardedJedisPool shardedJedisPool, String lockId) {
        if (StringUtils.isBlank(lockId)) {
            throw new NullPointerException("锁标识为空");
        }
        if (shardedJedisPool == null) {
            throw new NullPointerException("Jedis客户端为空");
        }
        lockId = StringUtils.trim(lockId);

        this.lockId = lockId;
        this.shardedJedisPool = shardedJedisPool;
        this.readLockId = ROOT_PATH + READ_LOCK_NAME + lockId;
        this.writeLockId = ROOT_PATH + WRITE_LOCK_NAME + lockId;
        this.readMutex = new DistributedReentrantReadLock(shardedJedisPool, this.readLockId);
        this.writeMutex = new DistributedReentrantReadLock(shardedJedisPool, this.writeLockId);
    }

    @Override
    public ReentrantLock readLock() {
        return this.readMutex;
    }

    @Override
    public ReentrantLock writeLock() {
        return this.writeMutex;
    }

    class DistributedReentrantReadLock implements ReentrantLock {

        /**
         * 重试等待时间
         */
        @Getter
        private int retryAwait = 300;
        @Getter
        private int lockTimeout = 2000;
        @Getter
        protected String lockId;
        @Getter
        protected ShardedJedisPool shardedJedisPool;
        // 缓存线程对应的锁信息
        private final ConcurrentMap<Thread, LockData> threadData = new ConcurrentHashMap<>();

        public DistributedReentrantReadLock(ShardedJedisPool shardedJedisPool, String lockId) {
            this.lockId = lockId;
            this.shardedJedisPool = shardedJedisPool;
        }

        @Override
        public void lock() {
            tryLock(Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
        }

        @Override
        public boolean tryLock(long timeout, TimeUnit unit) {
            return false;
        }

        @Override
        public void unlock() {

        }

    }

    static class DistributedReentrantWriteLock implements ReentrantLock {

        /**
         * 重试等待时间
         */
        @Getter
        private int retryAwait = 300;
        @Getter
        private int lockTimeout = 2000;
        @Getter
        protected String lockId;
        @Getter
        protected ShardedJedisPool shardedJedisPool;

        public DistributedReentrantWriteLock(ShardedJedisPool shardedJedisPool, String lockId) {
            this.lockId = lockId;
            this.shardedJedisPool = shardedJedisPool;
        }

        @Override
        public void lock() {

        }

        @Override
        public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
            return false;
        }

        @Override
        public void unlock() {

        }
    }

    /**
     * 锁数据
     */
    private static class LockData {
        final String lockVal;
        final Thread owningThread;
        final AtomicInteger lockCount;

        private LockData(Thread owningThread, String lockVal) {
            lockCount = new AtomicInteger(1);
            this.owningThread = owningThread;
            this.lockVal = lockVal;
        }
    }
}
